/*!
 *  @file       led_driver.c
 *  @brief      led driver source file 
 *  @author     T4056 Student
 *  @date       2024-07-20
 *  @version    V1.0
 *  @par        Copyright(c):
 *              T4056 Student.All rights reserved. 
 */
#include "led_driver.h"

static int _init(pin_driver_t * me,pin_type_e pin_type);
static int _set_state(pin_driver_t * me,pin_type_e pin_type,GPIO_PinState state);
static int _get_state(pin_driver_t * me,pin_type_e pin_type,GPIO_PinState * state);
static int _toggle(pin_driver_t * me,pin_type_e pin_type);

static pin_driver_ops_t pin_driver_ops = {
	.pf_init = _init,
	.pf_set_state = _set_state,
	.pf_get_state = _get_state,
	.pf_toggle = _toggle,
};

/*!
 *	@brief		get port from name
 *	@details	name[0] means port from GPIOA to GPIOE
 *	@param		name
 *	@return		port
 */
static GPIO_TypeDef * get_port_from_name(const char * name)
{
	static GPIO_TypeDef * port_table[] = {GPIOA,GPIOB,GPIOC,GPIOD,GPIOE};
	return (GPIO_TypeDef *)port_table[name[0] - 'A'];
}

/*!
 *	@brief		get pin from name
 *	@details	name[5-6] means pin from GPIOA to GPIOE
 *	@param		name
 *	@return		pin
 */
static uint16_t get_pin_from_name(const char * name)
{
	char * str_pin = (char *)&name[5];
	uint16_t pin_num = strtol(str_pin,NULL,10);
	return (uint16_t)(1 << pin_num);
}

/*!
 *	@brief		get mode from name
 *	@details	name [2] and name [3] determine input/output and whether to pull up, pull down, or nop
 *	@param		name
 *	@return		mode
 */
static uint32_t get_mode_from_name(const char * name)
{
	uint32_t mode = 0;
	if(name[2] == 'O' && name[3] == 'P')
		mode = GPIO_MODE_OUTPUT_PP;
	else if(name[2] == 'O' && name[3] == 'D')
		mode = GPIO_MODE_OUTPUT_OD;
	else if(name[2] == 'I')
		mode = GPIO_MODE_INPUT;
	return mode;
}

/*!
 *	@brief		get pull from name
 *	@details	name [2] and name [3] determine input/output and whether to pull up, pull down, or nop
 *	@param		name
 *	@return		pull
 */
static uint32_t get_pull_from_name(const char * name)
{
	uint32_t pull = GPIO_NOPULL;
	if(name[2] == 'I' && name[3] == 'U')
		pull = GPIO_PULLUP;
	else if(name[2] == 'I' && name[3] == 'D')
		pull = GPIO_PULLDOWN;
	return pull;
}

/*!
 *	@brief		gpio clk enable
 *	@details	just enable clk
 *	@param		name
 *	@return		none
 *	@note		There should be a global variable here to record the number of clock enablement times, 
 *				and subsequent functions should be added to determine whether to disable based on the number of clock enablement times
 */
static void gpio_clock_ebale(const char * name)
{
	GPIO_TypeDef * gpio = get_port_from_name(name);
	
	if(GPIOA == gpio)
		__HAL_RCC_GPIOA_CLK_ENABLE();
	else if(GPIOB == gpio)
		__HAL_RCC_GPIOB_CLK_ENABLE();
	else if(GPIOC == gpio)
		__HAL_RCC_GPIOC_CLK_ENABLE();
	else if(GPIOD == gpio)
		__HAL_RCC_GPIOD_CLK_ENABLE();
	else if(GPIOE == gpio)
		__HAL_RCC_GPIOE_CLK_ENABLE();
}

/*!
 *	@details	get pin attribute from name
 *	@note		it's should add some assert or trace function
 */
int pin_driver_register(pin_driver_t * me,pin_type_e pin_type,const char * pin_name)
{
	if(strlen(pin_name) != 7)
		return 1;
	
	me->pin_attr[pin_type].name = (char *)pin_name;
	me->pin_attr[pin_type].port = get_port_from_name(me->pin_attr[pin_type].name);
	me->pin_attr[pin_type].pin = get_pin_from_name(me->pin_attr[pin_type].name);
	me->pin_attr[pin_type].mode = get_mode_from_name(me->pin_attr[pin_type].name);
	me->pin_attr[pin_type].pull = get_pull_from_name(me->pin_attr[pin_type].name);
	
	me->ops = &pin_driver_ops;
	me->ops->pf_init(me,pin_type);
	me->ops->pf_set_state(me,pin_type,GPIO_PIN_SET);
	
	return 0;
}

/*!
 *	@brief		pin init
 *	@details	1.enable clk 2.init 3.if pin is output mode,set pin state
 *	@param		me
 *	@param		pin_type
 *	@return		0:success
 *	@note		it should add some assert or trace function
 */
static int _init(pin_driver_t * me,pin_type_e pin_type)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	if(me->pin_attr[pin_type].init == 0)
	{
		gpio_clock_ebale(me->pin_attr[pin_type].name);
		GPIO_InitStruct.Mode = me->pin_attr[pin_type].mode;
		GPIO_InitStruct.Pull = me->pin_attr[pin_type].pull;
		GPIO_InitStruct.Pin = me->pin_attr[pin_type].pin;
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
		HAL_GPIO_Init(me->pin_attr[pin_type].port,&GPIO_InitStruct);
		HAL_GPIO_WritePin(me->pin_attr[pin_type].port,me->pin_attr[pin_type].pin,GPIO_PIN_RESET);
		if(me->pin_attr[pin_type].mode == GPIO_MODE_OUTPUT_PP || me->pin_attr[pin_type].mode == GPIO_MODE_OUTPUT_OD)
			me->pin_attr[pin_type].state = GPIO_PIN_RESET;
		me->pin_attr[pin_type].init = 1;
	}
	return 0;
}

/*!
 *	@brief		pin set state
 *	@details	1.determine whether the pin is in output mode 2.if the state changes,set pin state
 *	@param		me,see pin_driver_t
 *	@param		pin_type,see pin_type_e
 *	@param		state
 *	@return		0:success
 *				1:failure
 *	@note		it should add some assert or trace function
 */
static int _set_state(pin_driver_t * me,pin_type_e pin_type,GPIO_PinState state)
{
	if(me->pin_attr[pin_type].mode == GPIO_MODE_OUTPUT_OD || me->pin_attr[pin_type].mode == GPIO_MODE_OUTPUT_PP)
	{
		if(state != me->pin_attr[pin_type].state)
		{
			me->pin_attr[pin_type].state = state;
			HAL_GPIO_WritePin(me->pin_attr[pin_type].port,me->pin_attr[pin_type].pin,me->pin_attr[pin_type].state);
		}
		return 0;
	}
	return 1;
}

/*!
 *	@brief		pin get state
 *	@details	1.determine whether the pin is in input mode 2.get the state and save to driver struct
 *	@param		me,see pin_driver_t
 *	@param		pin_type,see pin_type_e
 *	@param		*state
 *	@return		0:success
 *				1:failure
 *	@note		it should add some assert or trace function
 */
static int _get_state(pin_driver_t * me,pin_type_e pin_type,GPIO_PinState * state)
{
	if(me->pin_attr[pin_type].mode == GPIO_MODE_INPUT)
	{
		me->pin_attr[pin_type].state = HAL_GPIO_ReadPin(me->pin_attr[pin_type].port,me->pin_attr[pin_type].pin);
		* state = me->pin_attr[pin_type].state;
		return 0;
	}
	return 1;
}

/*!
 *	@brief		pin get toggle
 *	@details	1.get pin state from driver struct 2.change pin state 3.save pin state to driver struct 4.set pin state
 *	@param		me,see pin_driver_t
 *	@param		pin_type,see pin_type_e
 *	@return		0:success
 *	@note		it should add some assert or trace function
 */
static int _toggle(pin_driver_t * me,pin_type_e pin_type)
{
	GPIO_PinState PinState;
	if(me->pin_attr[pin_type].mode == GPIO_MODE_OUTPUT_OD || me->pin_attr[pin_type].mode == GPIO_MODE_OUTPUT_PP)
	{
		PinState = me->pin_attr[pin_type].state;
		if(PinState == GPIO_PIN_RESET)
			PinState = GPIO_PIN_SET;
		else
			PinState = GPIO_PIN_RESET;
		me->pin_attr[pin_type].state = PinState;
		HAL_GPIO_WritePin(me->pin_attr[pin_type].port,me->pin_attr[pin_type].pin,me->pin_attr[pin_type].state);
	}
	return 0;
}
